﻿namespace Lilium.LParse.Data

open Lilium.LParse.Prelude

/// Fast mutable sliding buffer.
type SlidingBuffer<'a> (?initialSize : int) = 
   
   let mutable pos = 0
   let mutable itemCount = 0
   let mutable buffer = Array.zeroCreate (defaultArg initialSize 4)
   
   let grow () =
       let newBuffer = Array.zeroCreate (buffer.Length * 2)
       match (pos - itemCount) with
       | startPos when startPos >= 0 -> 
           Array.blit buffer startPos newBuffer 0 itemCount 
       | startPos -> // startPos отрицателен
           Array.blit buffer (buffer.Length + startPos) newBuffer 0 (-startPos)
           Array.blit buffer 0 newBuffer (-startPos) pos
       pos <- 0
       buffer <- newBuffer

   /// returns n-th element from the end of the buffer without removing it.
   /// 1-based, internal use only.
   let fromEnd1 n : 'a = 
       match (pos - n) with
       | itemPos when itemPos >= 0 -> buffer.[itemPos]
       | itemPos -> buffer.[buffer.Length + itemPos]

   /// Discards n members from the begining of the buffer.
   /// It's possible to discard more elements than buffer actually has, in this case all elements will be discarded.
   member self.Discard n =
       match n with
       | n when n < 0 -> failwith "Non-negative argument excepted"
       | n when n < itemCount -> itemCount <- itemCount - n
       | _ -> pos <- 0
              itemCount <- 0

   /// returns n-th element from the end of the buffer without removing it.
   /// 0 retruns last element in the buffer, only none-negative integers allowed
   member self.FromEnd n =
       match n with
       | n when n < 0 -> failwith "Non-negative argument excepted"
       | _ -> fromEnd1 (n + 1)

   /// returns n-th element from the end of the buffer without removing it.
   /// 1 retruns last element in the buffer, only positive integers allowed
   member self.FromEnd1 n =
       match n with
       | n when n <= 0 -> failwith "Positive argument excepted"
       | _ -> fromEnd1 n


   /// Appends an item to the end of the buffer
   member self.Push item =
       if (itemCount = buffer.Length)
           then grow ()
       undefined ()

   /// Removes an item form the begining of the buffer
   member self.Pop () =
       match itemCount with
       | 0 -> failwith "Buffer is empty."
       | _ -> let result = fromEnd1 itemCount
              itemCount <- itemCount - 1
              result